import sim
import modem
import base64
import ubinascii
import ucryptolib
from usr.libs.datetime import DateTime


class SerialNumber(object):
    number_iterator = iter(range(0, 10000))

    @classmethod
    def get(cls):
        try:
            return next(cls.number_iterator)
        except StopIteration:
            cls.number_iterator = iter(range(0, 10000))
            return next(cls.number_iterator)


class DataType(object):
    DOWN_LINK_REQUEST = "1"
    DOWN_LINK_RESPONSE = "2"
    UP_LINK_REQUEST = "3"
    UP_LINK_RESPONSE = "4"


class Message(object):
    """[设备号,ICCID,流水号,接口唯一标识,报文类型,时间戳,消息长度,消息体]"""

    def __init__(self, teno=None, iccid=None, serialno=None, infname=None, datatype=None, timestamp=None, args=()):
        self.infname = infname
        self.args = args
        self.datatype = datatype
        self.teno = teno
        self.timestamp = timestamp
        self.serialno = serialno
        self.iccid = iccid

    def __str__(self):
        return self.to_string()

    def __format_string(self):
        string = getattr(self, "__format_string__", None)
        if string:
            return string
        body = "@".join(self.args)
        head = ",".join(
            (
                self.teno,
                self.iccid,
                self.serialno,
                self.infname,
                self.datatype,
                self.timestamp,
                str(len(body))
            )
        )
        string = "[" + ",".join([head, body]) + "]"
        setattr(self, "__format_string__", string)
        return string

    def to_string(self):
        return self.__format_string()

    @classmethod
    def from_string(cls, string):
        if not (string[0] == "[" and string[-1] == "]"):
            raise ValueError("message format error, start&end flag not valid!")
        parts = string[1:-1].split(",")
        head_parts = parts[:-1]
        args = parts[-1].split("@")
        self = cls(
            teno=head_parts[0],
            iccid=head_parts[1],
            serialno=head_parts[2],
            infname=head_parts[3],
            datatype=head_parts[4],
            timestamp=head_parts[5],
            args=tuple(args)
        )
        return self

    def to_bytes(self):
        return self.to_string().encode()

    @classmethod
    def from_bytes(cls, raw):
        return cls.from_string(raw.decode())


class EncryptWrapper(object):
    KEY = ubinascii.unhexlify("06afa26ff722d9fa276228e444fd9b9e")
    IV = ubinascii.unhexlify("1e8634114d5d1c04a597a6437c3dce92")
    MANUFACTURE_ID = "Quectel"
    PROTO_VERSION = "1.0.0"
    FIRMWARE_VERSION = modem.getDevFwVersion()

    def __init__(self, message):
        if not isinstance(message, Message):
            raise TypeError("`message` must be instance of class Message")
        self.message = message

    @classmethod
    def encrypt(cls, data):
        aes = ucryptolib.aes(cls.KEY, ucryptolib.MODE_CBC, cls.IV)
        out = bytearray()
        for index in range(0, len(data), 16):
            temp = data[index:index+16]
            count = 16 - len(temp)
            if count > 0:
                temp += chr(count) * count
                out += aes.encrypt(temp)
                break
            else:
                out += aes.encrypt(temp)
        else:
            out += aes.encrypt(chr(16) * 16)
        return base64.b64encode(out).decode()

    @classmethod
    def decrypt(cls, data):
        aes = ucryptolib.aes(cls.KEY, ucryptolib.MODE_CBC, cls.IV)
        out = bytearray()
        bdata = base64.b64decode(data)
        for decrypted_data in (
            (
                aes.decrypt(temp) for temp in
                (
                    bdata[index:index+16] for index in
                    (
                        _ for _ in range(0, len(bdata), 16)
                    )
                )
            )
        ):
            out += decrypted_data
        msg = out.decode()
        return msg[0:-ord(msg[-1])]

    def __format_string(self):
        string = getattr(self, "__format_string__", None)
        if string:
            return string
        string = "#".join(
            [
                self.encrypt(self.message.to_string()),
                self.MANUFACTURE_ID,
                self.PROTO_VERSION,
                self.FIRMWARE_VERSION
            ]
        ) + "\r\n"
        setattr(self, "__format_string__", string)
        return string

    def to_string(self):
        return self.__format_string()

    @classmethod
    def from_string(cls, string):
        msg_string, manufacture_id, proto_version, firmware_version = string.strip("\r\n").split("#")
        return Message.from_string(cls.decrypt(msg_string)), manufacture_id, proto_version, firmware_version

    def to_bytes(self):
        return self.to_string().encode()

    @classmethod
    def from_bytes(cls, raw):
        return cls.from_string(raw.decode())


class RequestMessage(Message):
    datatype = DataType.UP_LINK_REQUEST
    infname = "LOGIN"

    def __init__(self, *args):
        super().__init__(
            infname=self.infname,
            args=args,
            datatype=self.datatype,
            teno=self.get_teno(),
            timestamp=str(DateTime.now().timestamp),
            iccid=self.get_iccid(),
            serialno=str(SerialNumber.get())
        )

    @classmethod
    def get_iccid(cls):
        iccid = getattr(cls, "iccid", None)
        if iccid is None:
            iccid = sim.getIccid()
            if iccid == -1:
                raise ValueError("get iccid failed")
            else:
                setattr(cls, "iccid", iccid)
        return getattr(cls, "iccid")

    @classmethod
    def get_teno(cls):
        imei = getattr(cls, "imei", None)
        if imei is None:
            imei = modem.getDevImei()
            if imei == -1:
                raise ValueError("get imei failed")
            else:
                setattr(cls, "imei", imei)
        return getattr(cls, "imei")


class LoginMessage(RequestMessage):
    """设备登录"""
    infname = "LOGIN"
